/*
 * LanguageAccess.java
 *
 * Copyright(c) 2010
 * Michael Kroez
 * kroez@studiorutton.de
 *
 * All rights reserved.
 *
 * $Id$
 * 
 */

package de.studiorutton.gerengdic.databaseaccess;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Locale;

import android.util.Log;

import de.studiorutton.gerengdic.databaseaccess.DatabaseAccessException.ErrorCode;
import de.studiorutton.gerengdic.databaseaccess.records.IndexPositionRecord;
import de.studiorutton.gerengdic.databaseaccess.records.IndexRecord;
import de.studiorutton.gerengdic.databaseaccess.records.SuperIndexRecord;
import de.studiorutton.tre.AMatchResult;
import de.studiorutton.tre.SimpleTre;
import de.studiorutton.tre.TreException;

public class LanguageAccess {
	private final static String TAG = "learningwords";

	private String file;
	private String language;
	private SuperIndexAccess superIndex;
	private IndexPositionAccess indexPosition;

	public LanguageAccess(String file, String language, int superIndexBegin,
			int superIndexEnd, int indexBegin, int indexEnd, int lastViewOffset)
			throws DatabaseAccessException {
		this.file = file;
		this.language = language;
		this.superIndex = new SuperIndexAccess(superIndexBegin, superIndexEnd);
		this.indexPosition = new IndexPositionAccess(indexBegin, indexEnd,
				lastViewOffset);
	}

	public void close() throws DatabaseAccessException {
		indexPosition.close();
	}

	public String getLanguage() {
		return language;
	}

	public SuperIndexAccess getSuperIndexAccess() {
		return superIndex;
	}

	public IndexPositionAccess getIndexPositionAccess() {
		return indexPosition;
	}

	public String toString() {
		return "Language:" + language + " SuperIndexBegin:" + superIndex
				+ " IndexPositionBegin:" + indexPosition;
	}

	public class IndexPositionRange {
		public int begin;
		public int end;
	}

	public class SuperIndexAccess {
		private RandomAccessFile raf;
		private ArrayList<SuperIndexRecord> sirl;
		private byte[] buf = new byte[128];
		private int superIndexBegin;
		private int superIndexEnd;

		public SuperIndexAccess(int superIndexBegin, int superIndexEnd)
				throws DatabaseAccessException {
			this.superIndexBegin = superIndexBegin;
			this.superIndexEnd = superIndexEnd;
			try {
				this.raf = new RandomAccessFile(LanguageAccess.this.file, "r");
			} catch (FileNotFoundException e) {
				throw new DatabaseAccessException(
						ErrorCode.ERROR_CANNOT_READ_FILE);
			}
			sirl = new ArrayList<SuperIndexRecord>();

			readSuperIndexRecords();
		}

		public ArrayList<SuperIndexRecord> getSuperIndexRecords() {
			return sirl;
		}

		public void readSuperIndexRecords() throws DatabaseAccessException {
			// [ byte SUPER_INDEX_RECORD_HEADER | 4bytes keyBeginSize | n bytes
			// keyBegin | 4bytes indexStarting |
			// 4byte keyEndSize | n bytes keyEnd | | 4bytes indexEnding ]

			try {
				raf.seek(superIndexBegin);
				while (raf.getChannel().position() < superIndexEnd) {
					byte header = raf.readByte();
					if (header < 0
							|| header != SuperIndexRecord.SUPER_INDEX_RECORD_HEADER)
						throw new DatabaseAccessException(
								ErrorCode.ERROR_CANNOT_FIND_SUPERINDEX_HEADER);
					int keySize = raf.readInt();
					raf.read(buf, 0, keySize);
					String key = new String(buf, 0, keySize,
							DatabaseAccess.CHARSET_DEFAULT);
					int index = raf.readInt();

					SuperIndexRecord sir = new SuperIndexRecord(key, index);
					sirl.add(sir);
				}
				raf.close();
			} catch (IOException e) {
				throw new DatabaseAccessException(
						ErrorCode.ERROR_READING_DICTIONARY,
						e.getLocalizedMessage());
			}
		}

		public String toString() {
			return String.format("%d-%d", superIndexBegin, superIndexEnd);
		}

		public IndexPositionRange searchSuperIndex(String searchString) {
			IndexPositionRange ipr = new IndexPositionRange();
			String searchStringComparable = searchString
					.toLowerCase(Locale.GERMAN).replace("ä", "ae")
					.replace("ö", "oe").replace("ü", "ue");
			int searchStringLength = searchStringComparable.length();
			
			Iterator<SuperIndexRecord> iter = sirl.iterator();
			boolean beginFound = false;
			boolean endFound = false;
			SuperIndexRecord sir = null;
			SuperIndexRecord sirLast = sirl.get(0);
			while (iter.hasNext() && !endFound) {
				sir = iter.next();
				
				if (!beginFound) {
					if (searchStringComparable.compareTo(sir.getKeyComparable()) >= 0) {
						sirLast = sir;
					} else {
						Log.d(TAG, "begin found " + sirLast.getKey() + ".");
						ipr.begin = sirLast.getIndex();
						beginFound = true;
					}
				}

				if (beginFound && !endFound) {
					String keyComparable = sir.getKeyComparable();
					int minLength = Math.min(searchStringLength, keyComparable.length()); 
					
					if (searchStringComparable.compareTo(sir.getKeyComparable().substring( 0, minLength - 1)) >= 0) {
						continue;
					} else {
						Log.d(TAG, "end found " + sir.getKey() + ".");
						ipr.end = sir.getIndex(); 
						endFound = true;
					}
				}
			}
			if (!beginFound)
				ipr.begin = sir.getIndex();

			if (!endFound)
				ipr.end = -1;

			return ipr;
		}
	}

	public class IndexPositionAccess {
		private RandomAccessFile raf;
		private int indexPositionBegin;
		private int indexPositionEnd;
		private int lastViewOffset;
		private IndexAccess ia;
		private SimpleTre simpleTre;

		// [ INDEX_POSITION_RECORD_HEADER | 4 bytes offset position | 4 bytes
		// index record listlength | 4 bytes index record position ]

		public IndexPositionAccess(int indexPositionBegin,
				int indexPositionEnd, int lastViewOffset)
				throws DatabaseAccessException {
			try {
				this.raf = new RandomAccessFile(LanguageAccess.this.file, "r");
			} catch (FileNotFoundException e) {
				throw new DatabaseAccessException(
						ErrorCode.ERROR_CANNOT_READ_FILE);
			}

			// System.out.println( "indexPositionBegin=" + indexPositionBegin +
			// " indexPositionEnd=" + indexPositionEnd + " lastViewOffset=" +
			// lastViewOffset );
			this.indexPositionBegin = indexPositionBegin;
			this.indexPositionEnd = indexPositionEnd;
			this.lastViewOffset = lastViewOffset;
			try {
				raf.seek(indexPositionBegin);
			} catch (IOException e) {
				throw new DatabaseAccessException(
						ErrorCode.ERROR_READING_DICTIONARY,
						e.getLocalizedMessage());
			}
			ia = new IndexAccess();
			simpleTre = new SimpleTre();
		}

		public IndexAccess getIndexAccess() {
			return ia;
		}

		public void close() throws DatabaseAccessException {
			try {
				raf.close();
			} catch (IOException e) {
				throw new DatabaseAccessException(
						ErrorCode.ERROR_READING_DICTIONARY,
						e.getLocalizedMessage());
			}
		}

		public int getLastViewOffset() {
			return lastViewOffset;
		}

		public int getIndexSize() {
			return (indexPositionEnd - indexPositionBegin)
					/ IndexPositionRecord.INDEX_POSITION_RECORD_SIZE;
		}

		public class IndexAll {
			public IndexPositionRecord indexPositionRecord;
			public IndexRecord indexRecord;

			public IndexAll(IndexPositionRecord indexPositionRecord,
					IndexRecord indexRecord) {
				this.indexPositionRecord = indexPositionRecord;
				this.indexRecord = indexRecord;
			}

			public String toString() {
				return indexRecord.getKey();
			}
		}

		public void searchIndex(String searchPattern, boolean aMatchEnabled, IRecordListener rl)
				throws DatabaseAccessException {
			IndexPositionRange ipr = new IndexPositionRange();
			ipr.begin = indexPositionBegin;
			ipr.end = -1;
			searchIndex(searchPattern, aMatchEnabled, ipr, rl);
		}

		public void searchIndex(String searchPattern, boolean aMatchEnabled,
				IndexPositionRange ipr, IRecordListener rl) throws DatabaseAccessException {

//			Log.d( TAG, "before indexPositionEnd = " + ipr.end );
			if (ipr.end < 0)
				ipr.end = this.indexPositionEnd - IndexPositionRecord.INDEX_POSITION_RECORD_SIZE;
//			Log.d( TAG, "indexPositionEnd = " + ipr.end );
			try {
				simpleTre.compileRegex(searchPattern, SimpleTre.FULL_BEGINNING,
						true, true, aMatchEnabled);
				raf.seek(ipr.begin);
				while (raf.getChannel().position() <= ipr.end) {
					int index = (int) (raf.getChannel().position() - indexPositionBegin)
							/ IndexPositionRecord.INDEX_POSITION_RECORD_SIZE;
					byte header = raf.readByte();
					if (header < 0
							|| header != IndexPositionRecord.INDEX_POSITION_RECORD_HEADER) {
//						Log.d( TAG, "header = " + header + " position = " + raf.getChannel().position() + " ipr.end = " + ipr.end );
						throw new DatabaseAccessException( ErrorCode.ERROR_CANNOT_FIND_INDEXPOSITION_HEADER );

					}
					int viewOffset = raf.readInt();
					int numOfRecords = raf.readInt();
					int indexRecordPosition = raf.readInt();

					IndexRecord ir = ia.getIndexRecord(indexRecordPosition);
//					Log.d(TAG, "comparing searchpattern " + searchPattern
//							+ " with " + ir.getKey() + ".");
					AMatchResult matchResult = new AMatchResult();
					if (simpleTre.matchRegexFirst(ir.getKey(), matchResult) == SimpleTre.MATCH_FOUND) {
						IndexPositionRecord indexPositionRecord = new IndexPositionRecord(
								index, viewOffset, numOfRecords,
								indexRecordPosition);
//						Log.d( TAG, ir.getKey() + " Result: " + matchResult);
						rl.newRecord(new IndexAll( indexPositionRecord, ir ), matchResult );
					}
				}
			} catch (IOException e) {
				throw new DatabaseAccessException(
						ErrorCode.ERROR_READING_DICTIONARY,
						e.getLocalizedMessage());
			} catch (TreException e) {
				throw new DatabaseAccessException(
						ErrorCode.ERROR_MATCHING_STRING, searchPattern,
						e.getLocalizedMessage());
			}
		}

		public IndexPositionRecord getIndexPositionRecordAtIndex(int index)
				throws DatabaseAccessException {
			int indexPositionRecordP = indexPositionBegin
					+ IndexPositionRecord.INDEX_POSITION_RECORD_SIZE * index;
			if (indexPositionRecordP >= indexPositionEnd)
				return null;
			try {
				raf.seek(indexPositionRecordP);
				byte header = raf.readByte();
				if (header < 0
						|| header != IndexPositionRecord.INDEX_POSITION_RECORD_HEADER)
					throw new DatabaseAccessException(
							ErrorCode.ERROR_CANNOT_FIND_INDEXPOSITION_HEADER);
				int viewOffset = raf.readInt();
				int numberOfRecords = raf.readInt();
				int recordPosition = raf.readInt();
				return new IndexPositionRecord(index, viewOffset,
						numberOfRecords, recordPosition);
			} catch (IOException e) {
				throw new DatabaseAccessException(
						ErrorCode.ERROR_READING_DICTIONARY,
						e.getLocalizedMessage());
			}
		}

		public String toString() {
			return String.format("%d", indexPositionBegin);
		}
	}

	public class IndexAccess {
		private RandomAccessFile raf;
		byte[] buf = new byte[1024];

		public IndexAccess() throws DatabaseAccessException {
			try {
				this.raf = new RandomAccessFile(LanguageAccess.this.file, "r");

			} catch (IOException e) {
				throw new DatabaseAccessException(
						ErrorCode.ERROR_READING_DICTIONARY,
						e.getLocalizedMessage());
			}
		}

		public void close() throws DatabaseAccessException {
			try {
				raf.close();
			} catch (IOException e) {
				throw new DatabaseAccessException(
						ErrorCode.ERROR_READING_DICTIONARY,
						e.getLocalizedMessage());
			}
		}

		// [ byte INDEX_RECORD_HEADER | 4bytes keySize | nbytes key | 4bytes
		// recordListSize | n * 4 bytes recordList

		public IndexRecord getIndexRecord(int indexRecordPosition)
				throws DatabaseAccessException {
			int keySize;
			try {
				raf.seek(indexRecordPosition);

				byte header = raf.readByte();
				if (header != IndexRecord.INDEX_RECORD_HEADER) {
					throw new DatabaseAccessException(
							ErrorCode.ERROR_CANNOT_FIND_INDEXRECORD_HEADER);
				}
				keySize = raf.readInt();
				raf.read(buf, 0, keySize);

				String key = new String(buf, 0, keySize,
						DatabaseAccess.CHARSET_DEFAULT);

				int recordListSize = raf.readInt();
				int i = 0;
				int crrRecordPosition;
				ArrayList<Integer> recordPositions = new ArrayList<Integer>();
				while (i < recordListSize) {
					crrRecordPosition = raf.readInt();
					recordPositions.add(crrRecordPosition);
					i++;
				}

				// System.out.println( "IndexRecordKey: " + key );
				IndexRecord ir = new IndexRecord(key, recordPositions);
				return ir;
			} catch (IOException e) {
				throw new DatabaseAccessException(
						ErrorCode.ERROR_READING_DICTIONARY,
						e.getLocalizedMessage());
			}
		}
	}
}
